也来说说Java中的锁--并发编程关键知识点

也来说说Java中的锁

一,什么是java中的锁

1,从java语法上来说,java中的锁,指的就是java给我们提供的Lock接口以及相关的实现类。

public interface Lock ,通常我们这样来创建锁对象:

Lock lock = new ReentrantLock(); 

2,从架构上来说,锁是一种同步机制;
3,从功能上来说,锁是用来控制多个线程访问共享资源的方式,防止多个线程同时访问共享资源。

二,从JVM的角度来看锁

    说起锁,就要说一下synchronized关键字。锁是JDK5开始才引入的一个功能,而synchronized
关键字在JDK5之前就已经存在了,并且在没有锁的日子里,java就靠synchronized关键字来实现锁
的功能的。锁与synchronized关键字也相似的功能,区别就在于锁是显示的,而synchronized关键
字是隐式的。

1,锁的内存语义
    锁是一种同步机制,锁可以让临界区互斥的执行,在多线程环境下实现强同步,并且锁L在释放
时,释放锁L的A线程会通知获得锁L的B线程。这就是锁的内存语义。

2,锁的释放和获取的内存语义
    第一点:锁的释放的内存语义是:当线程A释放锁时,Java内存模型会把本地内存中的共享变量
的值刷新到主内存中。
    第二点:锁的获取的内存语义是:当线程A获取锁时,Java内存模型会把本地内存中的共享变量
的值置为无效,然后去主内存中读取新值。
    在这里补充说明一下,锁的释放和volatile关键字的写操作具有相同的内存语义,锁的获取和
volatile关键字的读具有相同的内存语义。并且从实现上来说,Java中锁机制的实现,依赖于
volatile关键字。
读者请思考:锁与volatile的不同点?

3,锁的内存语义的实现
    锁有很多种实现,这里我们仅仅以最常用的可重入锁ReentrantLock为例来说明,可重入锁
ReentrantLock的内存语义的实现是这样的:调用Lock方法获取锁,调用unlock方法释放锁。可重入
锁的实现依赖于AQS(AbstractQueueSynchronizer)同步器框架。而AQS实现的关键就是volatile关
键字,因此我们通常说,锁的内存语义的实现,依赖于volatile关键字。

三,从功能实现和使用的角度来看锁

    Java支持多线程,也就是多个线程可以同时访问同一个变量或者对象,由于每个对象都拥有这个
变量的拷贝,所以在程序的执行过程中,某个线程看到的变量的值不一定是最新的,因此就产生了线
程同步的问题。之所以每个线程都拥有一份拷贝,是为了加快程序的运行速度,这是很多多核处理器
的共性。

1,锁的使用方法
锁的使用方法比较简单,通过下面一段代码,就可以熟悉锁是如何使用的。

package com.spider.java;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 
 * @author yangcq
 * @desctiption Java中的锁
 *
 */
public class LockLearning {
	// 定义一个全局变量,账户余额
	private static int account_balance = 1000000000;
	public static void main(String[] args) {
		// 创建可重入锁对象lock
		Lock lock = new ReentrantLock();	
		// 在执行一段代码之前获取锁
		lock.lock();	
		// 执行代码,修改余额的值,这里可以是我们项目中任何一个需要加锁的操作
		updateAccountBalance(account_balance);	
		// 在执行一段代码之后释放锁
		lock.unlock();
	}
	private static int updateAccountBalance(Integer account_balance){
		return account_balance - 1000;
	}	
}

下面我们来看一下Lock接口:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

lock():获取锁,调用该方法后,当前线程马上获得锁,当锁获得后,从该方法返回。
lockInterruptibly():获取锁(支持中断获取锁)
tryLock():获取锁(非阻塞的获取锁,调用该方法后,会立即返回)
tryLock(long time, TimeUnit unit):获取锁(支持超时获取锁)
unlock():释放锁
newCondition():获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,
才能调用该组件的wait方法,而调用后,当前线程将被释放锁。
源码中注释很详细,大家可以细细品味一番:

    /**
     * Returns a new {@link Condition} instance that is bound to this
     * {@code Lock} instance.
     *
     * <p>Before waiting on the condition the lock must be held by the
     * current thread.
     * A call to {@link Condition#await()} will atomically release the lock
     * before waiting and re-acquire the lock before the wait returns.
     *
     * <p><b>Implementation Considerations</b>
     *
     * <p>The exact operation of the {@link Condition} instance depends on
     * the {@code Lock} implementation and must be documented by that
     * implementation.
     *
     * @return A new {@link Condition} instance for this {@code Lock} instance
     * @throws UnsupportedOperationException if this {@code Lock}
     *         implementation does not support conditions
     */

2,锁与synchronized关键字的比较
说到锁,就不得不提锁的始祖synchronized关键字,首先来看一下synchronized关键字的使用。

package com.spider.java;
import java.util.logging.Logger;
/**
 * 
 * @author yangcq
 * @description synchronized关键字的使用方法
 *
 */
public class SynchronizedLearning {
	static Logger logger = Logger.getAnonymousLogger();
	public static void main(String[] args) {
		/**
		 * synchronized关键字实现同步有2种方式,同步方法和同步块
		 */
		// 同步块
		synchronized(SynchronizedLearning.class){
			logger.info("我是同步块...");
		}
		synchronizedMethod();
	}
	// 同步方法
	public static synchronized void synchronizedMethod(){
		logger.info("我是同步方法...");
	}
}

总结一下,锁与synchronized关键字都可以实现锁的功能,由于锁是诞生在
synchronized关键字之后,所以锁更具有先进性,它具有一些synchronized
关键字不具有的功能,主要有以下几个方面:
支持非阻塞的获取锁;
支持中断的获取锁;
支持超时获取锁;

四,AQS同步器框架(AbstractQueueSynchronizer)

    锁的实现,依赖的就是AQS,Lock接口的实现基本上都是通过聚合了一个同步
器的子类来完成线程访问控制的。AQS又叫队列同步器,是用来构建锁或者其他同
步组件的基础框架。AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队
列来完成资源获取线程的排队工作。
    AQS的主要使用方法是继承,子类通过继承同步器AQS,然后实现它的抽象方法
来管理同步状态。
    队列同步器AQS是锁的实现的关键,在锁的实现中聚合队列同步器,利用队列同
步器实现锁的语义。可以这样理解二者之间的关系:锁是面向使用者的,同步器是面
向锁的实现类。

1,同步框架AQS的使用说明
首先自定义一个锁,独占锁,也就是说在同一时刻只有一个线程可以获得锁。

package com.spider.java;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
 * 
 * @author yangcq
 * @description 自定义锁 - 独占锁UserDefineLock,也就是在同一时刻,只有一个线程可以获得锁
 *
 */
public class UserDefineLock implements Lock {

	// 使用队列同步器AQS以后,我们只需要将锁的操作代理到自定义的同步器上就可以了
	private final SynchronizerDefine synchronizerDefine = new SynchronizerDefine();
	@Override
	public void lock() {
		synchronizerDefine.acquire(1);
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
		synchronizerDefine.acquireInterruptibly(1);
	}

	@Override
	public boolean tryLock() {
		return synchronizerDefine.tryAcquire(1);
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		return synchronizerDefine.tryAcquireNanos(1, unit.toNanos(time));
	}

	@Override
	public void unlock() {
		synchronizerDefine.release(1);
	}

	@Override
	public Condition newCondition() {
		return synchronizerDefine.newCondition();
	}
	
	public boolean hasQueueThreads(){
		return synchronizerDefine.hasQueuedThreads();
	}
	
	public boolean isLocked(){
		return synchronizerDefine.isHeldExclusively();
	}

	// 静态内部类,自定义同步器
	private static class SynchronizerDefine extends AbstractQueuedSynchronizer{
		/**
		 * 
		 */
		private static final long serialVersionUID = -5572420543429760541L;
		// 判断当前锁是否处于占用状态
		protected boolean isHeldExclusively(){
			return getState() == 1;
		}
		// 当状态为0时,获取锁
		public boolean tryAcquire(int acquires){
			if(compareAndSetState(0,1)){
				// 官方注释:Sets the thread that currently owns exclusive access.
				setExclusiveOwnerThread(Thread.currentThread());
				return true;
			}
			return false;
		}
		// 释放锁之后,将状态设置为0
		protected boolean tryRelease(int releases){
			if(getState() == 0){
				throw new IllegalMonitorStateException();
			}
			setExclusiveOwnerThread(null);
			setState(0);
			return true;
		}
		// 返回一个Condition,每个Condition都包含一个condition实例
		Condition newCondition(){
			return new ConditionObject();
		}
	}
}

然后编写一个测试类,启动10个线程,竞争一个资源,每1.5秒打印一行信息

package com.spider.java;
import java.util.concurrent.locks.Lock;
import java.util.logging.Logger;
/**
 * 
 * @author yangcq
 * @description 队列同步器AbstractQueuedSynchronizer
 * @description public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
 *
 */
public class AbstarctQueueSynchronizerLearning {
	// 打印logger日志
	static Logger logger = Logger.getAnonymousLogger();
	// 主方法
	public static void main(String[] args){
		test();
	}
	public static void test(){
		final Lock lock = new UserDefineLock();
		class ProductThread extends Thread{
			public void run(){
				while(true){
					lock.lock();
					try{
						sleep(1500);
						logger.info(Thread.currentThread().getName());
						sleep(1500);
					}
					catch(Exception e){
						lock.unlock();
					}
					finally{
						lock.unlock();
					}
				}
			}
		}
		// 启动5个线程
		for(int i=0;i<10;i++){
			ProductThread productThread = new ProductThread();
			productThread.setDaemon(true);
			productThread.start();
		}
		// 每隔1.5秒 打印 -----------
		for(int i=0;i<10;i++){
			try {
				ProductThread.sleep(1500);
				logger.info("-----------");
			} 
			catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

控制台输出信息(由于竞争关系,控制台输出是随机的,每次运行结果不同):

2016-7-31 1:44:04 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:04 com.spider.java.AbstarctQueueSynchronizerLearning$1ProductThread run
信息: Thread-1
2016-7-31 1:44:06 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:07 com.spider.java.AbstarctQueueSynchronizerLearning$1ProductThread run
信息: Thread-1
2016-7-31 1:44:07 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:09 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:10 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:10 com.spider.java.AbstarctQueueSynchronizerLearning$1ProductThread run
信息: Thread-1
2016-7-31 1:44:12 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:13 com.spider.java.AbstarctQueueSynchronizerLearning$1ProductThread run
信息: Thread-3
2016-7-31 1:44:13 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:15 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:16 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:16 com.spider.java.AbstarctQueueSynchronizerLearning$1ProductThread run
信息: Thread-3
2016-7-31 1:44:18 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------

五,可重入锁 java.util.concurrent.locks.ReentrantLock

1,什么是可重入锁
    可重入锁,就是支持重新进入的锁,也就是支持同一个线程重复的获取同一个锁。可
重入锁与我们之前定义的独占锁,有什么区别呢,一个明显的区别就是,如果我们在一个
线程A获取锁之后,再次调用lock.lock()方法,此时,线程A会被阻塞,线程A自己把自己
阻塞了,如果这样修改一下,上面程序的运行结果就会变成这样:

2016-7-31 1:58:50 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:58:51 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:58:53 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:58:54 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:58:56 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:58:57 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:58:59 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:59:00 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:59:02 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:59:03 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------

2,可重入锁是如何实现的
    可重入锁java.util.concurrent.locks.ReentrantLock中,有一个方法用来判断当前线程
是否是获取锁的线程,也就是说如果是一个已经获取锁的线程A,再次调用lock.lock()方法时,
可重入锁会将同步状态值加增加,然后返回true,表示获取锁成功。源代码如下:

        /**
         * Performs non-fair tryLock.  tryAcquire is
         * implemented in subclasses, but both need nonfair
         * try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

这样处理以后,释放锁时,相应的也要改变处理逻辑:

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

3,可重入锁的公平与非公平竞争实现
    可重入锁支持以公平的方式,还是非公平的方式获取锁。设置很简单,通过下面的
代码,在创建锁对象时进行设置:

Lock lock = new ReentrantLock(true);    // 以公平的方式获取锁
Lock lock = new ReentrantLock(false);   // 以非公平的方式获取锁

源码如下:

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = (fair)? new FairSync() : new NonfairSync();
    }

这2种获取锁的方式有什么区别呢?区别就在于,以公平方式获取锁时,会判断当前线程是
不是第一个请求获取该锁的线程。如果是第一个请求获取该锁的线程,则获取锁成功;否则
获取锁失败。

    /**
     * Sync object for fair locks  以公平的方式获取锁
     */
    final static class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // 区别就在于这里,以公平方式获取锁时,会判断当前线程是不是
                // 第一个请求获取该锁的线程。
                if (isFirst(current) && compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
    /**
     * Sync object for non-fair locks 以非公平的方式获取锁
     */
    final static class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
        // 非公平方式的方法实现nonfairTryAcquire
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

六,读写锁 java.util.concurrent.locks.ReentrantReadWriteLock;

1,什么是读写锁
    读写锁这样一种锁,它允许多个线程在同一时刻同时访问某个资源。读写锁维护
了一对锁,一个读锁和一个写锁,其实原理就是读写分离,因为在以读为主的场景下,
大部分时间都是读取,我们都知道,多个读线程同时访问,始终能保持同步状态。所以
此时不加锁可以有效的提高程序的执行速度。

2,读写锁的实现原理
    读写锁的原理,其实就是维护了2个锁,一个读锁和一个写锁。在读操作时获取读
锁,写操作时获取写锁。当写锁被获取到时,后面的读写请求都被阻塞,写锁释放后,
后面的读写操作继续执行。

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable
    /** Inner class providing readlock  读锁(内部类)*/
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock 写锁(内部类)*/
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** Performs all synchronization mechanics 队列同步器AQS实现类*/
    private final Sync sync;

    读锁和写锁,都是ReentrantReadWriteLock的内部类,这2个锁,都是实现了Lock接口的
实现类。

public static class ReadLock implements Lock, java.io.Serializable   // 读锁
public static class WriteLock implements Lock, java.io.Serializable  // 写锁

七,Condition接口

1,Condition的作用
    Condition接口提供了类似Object的监视器方法,与锁配合可以实现等待/通知模式。我们
都知道,任意一个Java对象,都具有下面这几个方法:
wait();              // 等待
wait(long timeout);  // 超时等待
notify();            // 唤醒
notifyAll();         // 唤醒所有
这些方法我们很少用到,但是Java都提供给我们了。这些方法有什么作用呢?其实这些方法都
是Object对象的监视器,与synchronized关键字配合使用,可以实现等待/通知模式。
Condition接口也提供了类似的一些方法,源码如下:

public interface Condition {
    void await() throws InterruptedException;
    void awaitUninterruptibly();
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;
    void signal();
    void signalAll();
}

Condition接口与Lock配合,可以实现等待/通知模式。看到这里,大家可能有些疑惑了,
Object监视器    + synchronized关键字
Condition监视器 + Lock
这不就是等待/通知模式的升级版吗!

2,Condition的使用

package com.spider.java;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
/**
 * 
 * @author yangcq
 * @description Condition接口 + Lock 实现等待/通知模式
 * @description 测试类 ConditionLearningTest
 *
 */
public class ConditionLearning {
	static Logger logger = Logger.getAnonymousLogger(); // 日志
	Lock lock = new ReentrantLock();
	Condition condition_get = lock.newCondition();
	
	// 定义一个Map
	Map<String,String> map = new HashMap<String,String>();
	
	// 取值 -- 如果值为空,则等待...
	public void conditionWait() throws InterruptedException{
		map.put("yangcq", "");
		lock.lock();
		try{
			while("".equals(map.get("yangcq"))){
				logger.info("key 'yangcq' 为空,不能取值...");
				condition_get.await();
			}
			logger.info("key 'yangcq' 的值为:" + map.get("yangcq"));
		}
		finally{
			lock.unlock();
		}
	}
	// 赋值 -- 赋值以后,有值了,唤醒
	public void conditionSignal() throws InterruptedException{
		map.put("yangcq", "1988");
		lock.lock();
		try{
			if("".equals(map.get("yangcq"))){
				logger.info("key 'yangcq' 为空,不能让其他方法从这里取值...");
			}
			else{
				logger.info("key 'yangcq' 有值,可以唤醒取值的方法");
				// 唤醒取值线程(消费者线程)
				condition_get.signal();
			}
		}
		finally{
			lock.unlock();
		}
	}
}

测试类:ConditionLearningTest.java

package com.spider.java;
/**
 * 
 * @author yangcq
 * @description Condition接口 + Lock 实现等待/通知模式测试
 *
 */
public class ConditionLearningTest {
	// 创建ConditionLearning对象
	static ConditionLearning conditionLearning = new ConditionLearning();
	
	public static void main(String[] args) {
		getValue(); // 初始值为空,消费者线程等待...
		putValue(); // 生产者赋值以后,有值了,通知消费者线程,然后消费者线程唤醒
	}
	
	@SuppressWarnings("static-access")
	public static void getValue(){
		// 消费者线程
		class ConsumerThread extends Thread{
			public void run(){
				try {
					conditionLearning.conditionWait();
				} 
				catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		ConsumerThread ConsumerThread = new ConsumerThread();
		ConsumerThread.start();
		try {
			ConsumerThread.sleep(2000);
		} 
		catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void putValue(){
		// 生产者线程
		class ProductThread extends Thread{
			public void run(){
				try {
					conditionLearning.conditionSignal();
				} 
				catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		ProductThread ProductThread = new ProductThread();
		ProductThread.start();
	}
}

好了,写到这里,关于锁的讲解暂时告一段落,笔者水平有限,难免有不妥之处,请见谅。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值